%%%-------------------------------------------------------------------
%%% @author Anil Kumar S. R. <sranil@gmail.com>
%%% @copyright (C) 2010, 
%%% @doc
%%%
%%% @end
%%% Created : 27 Aug 2010 by Anil Kumar S. R. <sranil@gmail.com>
%%%-------------------------------------------------------------------
-module(b2bua_session).

-behaviour(gen_server).

%% API
-export([start/1]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
		 terminate/2, code_change/3]).

-define(SERVER, ?MODULE). 
-include("../include/b2bua.hrl").

-record(b2bua_session_state, {socket, data, sip_session_pid}).

%%%===================================================================
%%% API
%%%===================================================================

%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
%% @end
%%--------------------------------------------------------------------
start([Accept_Socket]) ->
	gen_server:start(?MODULE, [Accept_Socket], []).

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%%                     {ok, State, Timeout} |
%%                     ignore |
%%                     {stop, Reason}
%% @end
%%--------------------------------------------------------------------
init([Accept_Socket]) ->
	process_flag(trap_exit, true),
	{ok, #b2bua_session_state{socket = Accept_Socket, data = "", sip_session_pid = -1}}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @spec handle_call(Request, From, State) ->
%%                                   {reply, Reply, State} |
%%                                   {reply, Reply, State, Timeout} |
%%                                   {noreply, State} |
%%                                   {noreply, State, Timeout} |
%%                                   {stop, Reason, Reply, State} |
%%                                   {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
	Reply = ok,
	{reply, Reply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @spec handle_cast(Msg, State) -> {noreply, State} |
%%                                  {noreply, State, Timeout} |
%%                                  {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_cast({response, Status, Msg}, State) ->
    gen_server:cast(self(), {reply, Msg}),
    case Status of 
        exit ->
            gen_server:cast(self(), kill_process);
        _ ->
            nothing
    end,
    {noreply, State};

handle_cast({reply, Msg}, State) ->
    #b2bua_session_state{socket = Socket} = State,
    send_data(Socket, Msg),
    {noreply, State};

handle_cast(kill_process, State) ->
    #b2bua_session_state{sip_session_pid = SIP_Session_PID} = State,
    gen_server:cast(SIP_Session_PID, deregister),
    {stop, normal, State};

handle_cast(_Msg, State) ->
	{noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%%                                   {noreply, State, Timeout} |
%%                                   {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_info(Info, State) ->
	#b2bua_session_state{socket = Socket} = State,
	?DEBUG("Received - ~p~n", [Info]),
	catch inet:setopts(Socket, [{active, once}]),
	case Info of 
		timeout ->
			{noreply, State};
		{tcp_error, _, _} ->
			{noreply, State};
		{tcp_closed, _} ->
			gen_server:cast(self(), kill_process),
			{noreply, State};
		{tcp, _, Data} ->
			New_State = receiver(binary_to_list(Data), State),
			{noreply, New_State};
        {'EXIT', _, _} ->
            Msg = "<talk_response><return status='critical' code='110' desc='Error occurred in the server. Logging out'/></talk_response>",
            send_data(Socket, Msg),
            gen_server:cast(self(), kill_process),
			{noreply, State};
		_ ->
			{noreply, State}
	end.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
	#b2bua_session_state{socket = Socket} = State,
	io:format("b2bua_session Terminated ~n"),
	close(Socket).

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
	{ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================
receiver(Data, State) ->
	#b2bua_session_state{data = Old_Data} = State,
	Combined_Data = lists:append(Old_Data, Data),
	case catch get_packet(Combined_Data) of
		{received, Request} ->
			New_State = parse_request(Request, State),
			New_State#b2bua_session_state{data = ""};
		{received_more, Request, More_Data} ->
			New_State = parse_request(Request, State),
			New_State = State#b2bua_session_state{data = More_Data};
		received_less ->
			State#b2bua_session_state{data = Combined_Data};
		not_received ->
			State#b2bua_session_state{data = ""};
		_ ->
			State#b2bua_session_state{data = ""}
	end.

get_packet(Request) ->
	case catch re:split(Request, "\r\n\r\n", [{return,list}]) of
		[Header, Data] ->
			[Field, Value] = re:split(Header, ":", [{return,list}]),
			case catch Field of 
				"Content-Length" ->
					Content_Length = list_to_integer(string:strip(Value, both)),
					Data_Length = length(Data),
					if 
						Content_Length == Data_Length ->
							{received, Data};
						Content_Length < Data_Length ->
							{received_more, lists:sublist(Data, 1, Content_Length), lists:sublist(Data, Content_Length+1)};
						true ->
							received_less
					end;
				_ ->
					not_received
			end;
		[_] ->
			received_less;
		_ ->
			not_received
	end.
			
parse_request(Request, State) ->	
	#b2bua_session_state{sip_session_pid = SIP_Session_PID, socket = Socket} = State,
	case catch parse_xml:parse(Request) of
		[{register, R}] ->
            if 
                SIP_Session_PID == -1 ->
                    case catch sip_session:start_link([self()]) of 
                        {ok, PID} ->
                            case catch gen_server:call(PID, {register, R}, infinity) of 
                                {ok, Msg} ->
                                    send_data(Socket, Msg),
									New_State = State#b2bua_session_state{sip_session_pid = PID};
                                {error, Reason} ->
                                    send_data(Socket, Reason),
                                    gen_server:cast(self(), kill_process),
									New_State = State
                            end;
                        _ ->
                            Msg = "<talk_response><return type='register' status='critical' code='500' desc='Request Failed'/></talk_response>",
                            send_data(Socket, Msg),
                            gen_server:cast(self(), kill_process),
							New_State = State
                    end;
                true ->
                    gen_server:call(SIP_Session_PID, de_register, infinity),
                    case catch gen_server:call(SIP_Session_PID, {register, R}, infinity) of 
                        {ok, Msg} ->
                            send_data(Socket, Msg);
                        {error, Reason} ->
                            send_data(Socket, Reason),
                            gen_server:cast(self(), kill_process)
                    end,
                    New_State = State
            end;
        [{Type, R}] ->
            if
                SIP_Session_PID == -1 ->
                    Msg = "<talk_response><return status='critical' code='105' desc='User not logged in'/></talk_response>",
                    send_data(Socket, Msg),
                    gen_server:cast(self(), kill_process),
                    New_State = State;
                true ->
                    gen_server:cast(SIP_Session_PID, {Type, R}),
                    New_State = State
            end;
		_ ->
            Msg = "<talk_response><return status='error' code='400' desc='Bad Request'/></talk_response>",
            send_data(Socket, Msg),
			New_State = State
	end,
	New_State.

close(Socket) ->
	gen_tcp:close(Socket).

send_data(Socket, Response) ->
	Data = lists:flatten(Response),
	Header = io_lib:format("Content-Length: ~w", [length(Data)]),
	Message = [Header, "\r\n\r\n", Data],
    io:format("###################### ~s ~n", [lists:flatten(Message)]),
	gen_tcp:send(Socket, Message).

